package cn.memedai.router;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.AnimRes;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.memedai.router.matcher.AbsImplicitMatcher;
import cn.memedai.router.matcher.Matcher;
import cn.memedai.router.matcher.MatcherRegistry;
import cn.memedai.router.matcher.MatcherResult;

/**
 * Real router manager, a singleton.
 * <p>
 */
public class RealRouter {
    private static RealRouter sInstance;

    // interceptor's name -> interceptor instance
    private Map<String, WeakReference<RouteInterceptor>> mInterceptorInstance = new HashMap<>();
    private RouteOptions mRouteOptions;
    private Uri uri;

    private RealRouter() {
    }

    static RealRouter get() {
        if (sInstance == null) {
            synchronized (RealRouter.class) {
                if (sInstance == null) {
                    sInstance = new RealRouter();
                }
            }
        }
        sInstance.reset();
        return sInstance;
    }

    /**
     * Reset uri and options.
     */
    private void reset() {
        uri = null;
        mRouteOptions = new RouteOptions();
    }

    /**
     * Add custom route table.
     *
     * @param routeTable RouteTable
     * @see Router#addRouteTable(RouteTable)
     */
    void addRouteTable(RouteTable routeTable) {
        if (routeTable != null) {
            routeTable.handleActivityTable(AptHub.sActivityTable);
        }
    }

    RealRouter build(Uri uri) {
        this.uri = uri;
        return this;
    }

    /**
     * Route result callback.
     *
     * @param callback RouteCallback
     * @return this
     */
    public RealRouter callback(RouteCallback callback) {
        mRouteOptions.setCallback(callback);
        return this;
    }

    /**
     * Request code to start activity for result.
     *
     * @param requestCode requestCode
     * @return this
     */
    public RealRouter requestCode(int requestCode) {
        if (requestCode >= 0) {
            mRouteOptions.setRequestCode(requestCode);
        } else {
            RLog.w("Invalid requestCode");
        }
        return this;
    }

    /**
     * Add extra bundles.
     *
     * @param extras Bundle
     * @return this
     */
    public RealRouter extras(Bundle extras) {
        mRouteOptions.setBundle(extras);
        return this;
    }


    public RealRouter activityOptions(ActivityOptionsCompat activityOptions) {
        mRouteOptions.setActivityOptions(activityOptions);
        return this;
    }
    /**
     * Add additional flags to the intent (or with existing flags value).
     *
     * @param flags The new flags to set, such as {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}
     * @return this
     * @see Intent#addFlags(int)
     */
    @SuppressWarnings("unused")
    public RealRouter addFlags(int flags) {
        mRouteOptions.addFlags(flags);
        return this;
    }

    /**
     * Specify an explicit transition animation.
     *
     * @param enterAnim A resource ID of the animation resource to use for the incoming activity.
     *                  Use 0 for no animation.
     * @param exitAnim  A resource ID of the animation resource to use for the outgoing activity.
     *                  Use 0 for no animation.
     * @return this
     * @see Activity#overridePendingTransition(int, int)
     */
    public RealRouter anim(@AnimRes int enterAnim, @AnimRes int exitAnim) {
        mRouteOptions.setAnim(enterAnim, exitAnim);
        return this;
    }

    /**
     * Green channel, i.e. skip all the interceptors.
     *
     * @return this
     */
    @SuppressWarnings("unused")
    public RealRouter skipInterceptors() {
        mRouteOptions.setSkipInterceptors(true);
        return this;
    }

    /**
     * {@link RouteCallback}.
     */
    private void callback(RouteResult state, String msg) {
        if (state != RouteResult.SUCCEED) {
            RLog.w(msg);
        }
        if (mRouteOptions.getCallback() != null) {
            mRouteOptions.getCallback().callback(state, uri, msg);
        }
    }

    private void assembleIntent(Context context, Intent intent, RouteOptions routeOptions) {
        if (intent == null) {
            return;
        }
        if (routeOptions.getBundle() != null && !routeOptions.getBundle().isEmpty()) {
            intent.putExtras(routeOptions.getBundle());
        }
        if (!(context instanceof Activity)) {
            routeOptions.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        if (routeOptions.getFlags() != 0) {
            intent.addFlags(routeOptions.getFlags());
        }
    }

    private boolean intercept(Context context, Class<? extends Activity> target) {
        if (AptHub.sInterceptorTable.isEmpty()) {
            return false;
        }
        String interceptorClassname = AptHub.sInterceptorTable.get(target);
        if (interceptorClassname != null) {
            WeakReference<RouteInterceptor> weakReference = mInterceptorInstance.get(interceptorClassname);
            RouteInterceptor interceptor =null;
            if (weakReference != null) {
                 interceptor = weakReference.get();
                if (interceptor == null) {
                    interceptor = generateRouteInterceptor(interceptorClassname, interceptor);
                }
            }else{
                interceptor = generateRouteInterceptor(interceptorClassname, interceptor);
            }
            if (!mRouteOptions.isSkipInterceptors() && interceptor != null && interceptor.intercept(context, uri,
                    mRouteOptions.getBundle())) {
                interceptor.intercepted(context, uri, mRouteOptions.getBundle());
                return true;
            }
        }
        return false;
    }

    private RouteInterceptor generateRouteInterceptor(String interceptorClassname, RouteInterceptor interceptor) {
        try {
            Class<?> clz = Class.forName(interceptorClassname);
            Constructor<?> constructor = clz.getConstructor();
            interceptor = (RouteInterceptor) constructor.newInstance();
            mInterceptorInstance.put(interceptorClassname,new WeakReference<RouteInterceptor>(interceptor));
        } catch (Exception e) {
            RLog.e("Fail to Instance the " + interceptorClassname);
        }
        return interceptor;
    }

    /**
     * Generate an {@link Intent} according to the given uri.
     *
     * @param context Strongly recommend an activity sInstance.
     * @return Intent
     */
    @Nullable
    @SuppressWarnings("WeakerAccess")
    public Intent getIntent(Context context) {
        if (uri == null) {
            callback(RouteResult.FAILED, "uri == null.");
            return null;
        }

        if (!mRouteOptions.isSkipInterceptors()) {
            for (RouteInterceptor interceptor : Router.getGlobalInterceptors()) {
                if (interceptor.intercept(context, uri, mRouteOptions.getBundle())) {
                    interceptor.intercepted(context, uri, mRouteOptions.getBundle());
                    return null;
                }
            }
        }

        List<Matcher> matchers = MatcherRegistry.getMatcher();
        if (matchers.isEmpty()) {
            callback(RouteResult.FAILED, "The MatcherRegistry contains no Matcher.");
            return null;
        }
        for (Matcher matcher : matchers) {
            RLog.i("尝试匹配:" + matcher.getClass().getCanonicalName());
            MatcherResult matcherResult = matcher.match(context, uri, AptHub.sActivityTable, mRouteOptions);
            if (matcherResult.isMatch()) {
                RLog.i("匹配到:" + matcher.getClass().getCanonicalName());
                // Ignore implicit intent.
                if (!(matcher instanceof AbsImplicitMatcher) &&
                        intercept(context, matcherResult.getMatchClass())) {
                    return null;
                }
                Intent intent = matcher.onMatched(context, uri, matcherResult.getMatchClass());
                assembleIntent(context, intent, mRouteOptions);
                return intent;
            }
        }

        callback(RouteResult.FAILED, "Can not find an Activity that matches the given uri: " + uri);
        return null;
    }

    /**
     * Execute transition.
     *
     * @param context Strongly recommend an activity sInstance.
     */
    public void go(Context context) {
        Intent intent = getIntent(context);
        if (intent == null) {
            return;
        }
        if (!(context instanceof Activity)) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        Bundle options = mRouteOptions.getActivityOptions() == null ?
                null : mRouteOptions.getActivityOptions().toBundle();

        if (context instanceof Activity) {
            ActivityCompat.startActivityForResult((Activity) context, intent,
                    mRouteOptions.getRequestCode(), options);

            if (mRouteOptions.getEnterAnim() != 0 && mRouteOptions.getExitAnim() != 0) {
                // Add transition animation.
                ((Activity) context).overridePendingTransition(
                        mRouteOptions.getEnterAnim(), mRouteOptions.getExitAnim());
            }
        } else {
            RLog.w("Please pass an Activity context to call method 'startActivityForResult'");
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            ActivityCompat.startActivity(context, intent, options);
        }
        callback(RouteResult.SUCCEED, null);
    }
}
